package club.janna.springboot.study.aspect;

import club.janna.springboot.study.annotation.JannaLimiter;
import com.alibaba.druid.util.StringUtils;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Slf4j
@Aspect
//@Component
public class JannaLimiterAspect {

    private final Map<String, RateLimiter> map = new ConcurrentHashMap<>();

    @Around("@annotation(club.janna.springboot.study.annotation.JannaLimiter)")
    public Object around(ProceedingJoinPoint pjp) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String uri = request.getRequestURI();
        if(StringUtils.isEmpty(uri))
            throw new RuntimeException("uri is null");
        JannaLimiter jannaLimiter = getLimiter(pjp);
        if(jannaLimiter == null)
            throw new RuntimeException("annotation not found!");

        RateLimiter limiter;
        if(!map.containsKey(uri))
            synchronized (map) {
                if(!map.containsKey(uri)) {
                    log.info("创建RateLimiter，key -> {}, permitsPerSecond -> {}", uri, jannaLimiter.value());
                    map.putIfAbsent(uri, (limiter = RateLimiter.create(jannaLimiter.value())));
                }
            }
        limiter = map.get(uri);
        if(!limiter.tryAcquire(jannaLimiter.timeout(), TimeUnit.MILLISECONDS))
            return jannaLimiter.msg();
        try {
            return pjp.proceed(pjp.getArgs());
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }

    private JannaLimiter getLimiter(ProceedingJoinPoint pjp) {
        Signature signature = pjp.getSignature();
        if(!(signature instanceof MethodSignature))
            throw new RuntimeException("JannaLimiter only support method");
        try {
            Method method = pjp.getTarget().getClass().getMethod(signature.getName(), ((MethodSignature) signature).getParameterTypes());
            return method.getAnnotation(JannaLimiter.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }
}
